package com.googlecode.horncomb.util.lang;

import java.beans.PropertyDescriptor;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.NullValueInNestedPathException;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
 * @author Daniel
 */
public abstract class BeanUtils extends org.springframework.beans.BeanUtils {
	public static void copyPropertiesAsPossible(Object source, Object target) {
		// BeanUtils.copyProperties(source, target);
		PropertyDescriptor[] targetPds = BeanUtils
				.getPropertyDescriptors(target.getClass());

		for (int i = 0; i < targetPds.length; i++) {
			PropertyDescriptor targetPd = targetPds[i];
			if (targetPd.getWriteMethod() != null) {
				PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(
						source.getClass(), targetPd.getName());
				if (sourcePd != null) {
					try {
						Object value = null;
						boolean find = false;
						if (sourcePd.getReadMethod() != null) {
							find = true;
							Method readMethod = sourcePd.getReadMethod();
							if (!Modifier.isPublic(readMethod
									.getDeclaringClass().getModifiers())) {
								readMethod.setAccessible(true);
							}
							value = readMethod.invoke(source, new Object[0]);
						} else {
							try {
								if (!find) {
									Method readMethod = getAccessibelReadMethod(
											sourcePd.getWriteMethod()
													.getDeclaringClass(),
											targetPd, true);
									if (readMethod != null) {
										find = true;
										value = readMethod.invoke(source,
												new Object[0]);
									}
								}

							} catch (NoSuchMethodException ex) {
								// ignore
							}
							try {
								if (!find) {
									Field field = getAccessibleField(sourcePd
											.getWriteMethod()
											.getDeclaringClass(), targetPd,
											true);
									if (field != null) {
										find = true;
										value = field.get(source);
									}
								}
							} catch (NoSuchFieldException ex) {
								// ignore
							}
						}
						if (find && value != null) {
							Method writeMethod = getAccessibelWriteMethod(
									target.getClass(), targetPd, true);
							if (!Modifier.isPublic(writeMethod
									.getDeclaringClass().getModifiers())) {
								writeMethod.setAccessible(true);
							}
							if (!targetPd.getPropertyType().isAssignableFrom(
									value.getClass())) {
								value = new BeanWrapperImpl(target)
										.convertIfNecessary(ObjectUtils
												.getDisplayString(value),
												targetPd.getPropertyType());
							}
							writeMethod.invoke(target, new Object[] { value });
						}
					} catch (Throwable ex) {
						throw new FatalBeanException(
								"Could not copy properties from source to target",
								ex);
					}
				}
			}
		}
	}

	public static Method getAccessibelReadMethod(Class<?> clz,
			PropertyDescriptor propertyDescriptor, boolean findAll)
			throws SecurityException, NoSuchMethodException {
		Assert.notNull(propertyDescriptor);
		Method readMethod = propertyDescriptor.getReadMethod();
		if (readMethod == null) {
			readMethod = getAccessibelReadMethod(clz, propertyDescriptor
					.getName(), findAll);
		} else {
			setMethodAccessibel(readMethod);
		}
		return readMethod;
	}

	public static Method getAccessibelReadMethod(Class<?> clz,
			String propertyName, boolean findAll) throws SecurityException,
			NoSuchMethodException {
		return getMethodAboutProperty(clz, propertyName, new String[] { "get",
				"is" }, new Class[0], findAll);
	}

	public static Method getAccessibelWriteMethod(Class<?> clz,
			PropertyDescriptor propertyDescriptor, boolean findAll)
			throws SecurityException, NoSuchMethodException {
		Assert.notNull(propertyDescriptor);
		Method writeMethod = propertyDescriptor.getWriteMethod();
		if (writeMethod == null) {
			writeMethod = getAccessibelWriteMethod(clz, propertyDescriptor
					.getName(), propertyDescriptor.getPropertyType(), findAll);
		} else {
			setMethodAccessibel(writeMethod);
		}
		return writeMethod;
	}

	public static Method getAccessibelWriteMethod(Class<?> clz,
			String propertyName, Class<?> propertyClass, boolean findAll)
			throws SecurityException, NoSuchMethodException {
		Assert.notNull(propertyClass);
		return getMethodAboutProperty(clz, propertyName,
				new String[] { "set" }, new Class[] { propertyClass }, findAll);
	}

	/**
	 * 设读取方法可访问
	 * @param clz
	 * @param propertyName
	 * @throws SecurityException
	 * @throws NoSuchMethodException
	 */
	private static Method getMethodAboutProperty(Class<?> clz,
			String propertyName, String[] prefixCandidate, Class<?>[] params,
			boolean findAll) throws SecurityException, NoSuchMethodException {
		Assert.notNull(clz);
		Assert.hasText(propertyName);
		Assert.notEmpty(prefixCandidate);

		boolean first = true;
		boolean finded = true;
		Method method = null;
		StringBuilder notSuchMethodMessage = new StringBuilder(
				"can not find read method: ");
		for (String prefix : prefixCandidate) {
			String methodName = prefix + StringUtils.capitalize(propertyName);
			try {
				method = getMethod(clz, methodName, params, findAll, true);
			} catch (NoSuchMethodException ex) {
				finded = false;
				if (!first) {
					first = false;
					notSuchMethodMessage.append(" or ");
				}
				notSuchMethodMessage.append(clz.getName()).append(".").append(
						methodName);
			}
		}
		if (!finded) {
			throw new NoSuchMethodException(notSuchMethodMessage.toString());
		}
		return method;
	}

	public static Method getMethod(Class<?> clz, String methodName,
			Class<?>[] params, boolean findAll) throws SecurityException,
			NoSuchMethodException {
		return getMethod(clz, methodName, params, findAll, false);
	}

	/**
	 * 设方法可访问
	 * @param clz
	 * @param methodName
	 * @param params
	 * @throws SecurityException
	 * @throws NoSuchMethodException
	 */
	public static Method getMethod(Class<?> clz, String methodName,
			Class<?>[] params, boolean findAll, boolean setAccessable)
			throws SecurityException, NoSuchMethodException {
		Assert.notNull(clz);
		Assert.hasText(methodName);
		Method method = null;
		try {
			method = (Method) getAccessibelObject(clz, "METHOD", methodName,
					params, findAll, setAccessable);
		} catch (NoSuchFieldException e) {
		}
		return method;
	}

	public static Field getAccessibleField(Class<?> clz,
			PropertyDescriptor propertyDescriptor, boolean findAll)
			throws SecurityException, NoSuchFieldException {
		return getAccessibleField(clz, propertyDescriptor.getName(), findAll);
	}

	public static Field getAccessibleField(Class<?> clz, String fieldName,
			boolean findAll) throws SecurityException, NoSuchFieldException {
		return getField(clz, fieldName, findAll, true);
	}

	public static Field getField(Class<?> clz, String fieldName, boolean findAll)
			throws SecurityException, NoSuchFieldException {
		return getField(clz, fieldName, findAll, false);
	}

	public static Field getField(Class<?> clz, String fieldName,
			boolean findAll, boolean setAccessable) throws SecurityException,
			NoSuchFieldException {
		Assert.notNull(clz);
		Assert.hasText(fieldName);
		Field field = null;
		try {
			for (Class<?> ifClz : clz.getInterfaces()) {
				try {
					field = ifClz.getField(fieldName);
				} catch (NoSuchFieldException ex) {
				}
				if (field != null) {
					if (setAccessable) {
						setFieldAccessibel(field);
					}
					return field;
				}
			}
			field = (Field) getAccessibelObject(clz, "FIELD", fieldName, null,
					findAll, setAccessable);
		} catch (NoSuchMethodException e) {
		}
		return field;
	}

	private static AccessibleObject getAccessibelObject(Class<?> clz,
			String type, String name, Class<?>[] params, boolean findAll,
			boolean setAccessable) throws SecurityException,
			NoSuchMethodException, NoSuchFieldException {
		Assert.notNull(clz);
		Assert.hasText(type);
		Assert.hasText(name);
		AccessibleObject result = null;
		NoSuchMethodException rootMethodEx = null;
		NoSuchFieldException rootFieldEx = null;
		if (type.toUpperCase().equals("FIELD")) {
			try {
				result = clz.getField(name);
			} catch (NoSuchFieldException ex) {
			}
		} else if (type.toUpperCase().equals("METHOD")) {
			try {
				result = clz.getMethod(name, params);
			} catch (NoSuchMethodException ex) {
			}
		}
		if (result == null) {
			try {
				if (type.toUpperCase().equals("FIELD")) {
					result = clz.getDeclaredField(name);
				} else if (type.toUpperCase().equals("METHOD")) {
					result = clz.getDeclaredMethod(name, params);
				}
			} catch (NoSuchMethodException ex) {
				rootMethodEx = ex;
			} catch (NoSuchFieldException ex) {
				rootFieldEx = ex;
			} finally {
				if (result == null) {
					Class<?> superClz = clz.getSuperclass();
					if (superClz == null) {
						result = recusiveFindAccessibleObject(superClz, type,
								name, params);
					}
				}
			}
		}
		if (result == null) {
			if (rootMethodEx != null) {
				throw rootMethodEx;
			} else if (rootFieldEx != null) {
				throw rootFieldEx;
			}
		} else if (setAccessable) {
			if (type.toUpperCase().equals("FIELD")) {
				setFieldAccessibel((Field) result);
			} else if (type.toUpperCase().equals("METHOD")) {
				setMethodAccessibel((Method) result);
			}
		}
		return result;
	}

	private static AccessibleObject recusiveFindAccessibleObject(Class<?> clz,
			String type, String name, Class<?>[] params)
			throws SecurityException {
		Assert.notNull(clz);
		Assert.hasText(name);
		AccessibleObject result = null;

		try {
			if (type.toUpperCase().equals("FIELD")) {
				result = clz.getDeclaredField(name);
			} else if (type.toUpperCase().equals("METHOD")) {
				result = clz.getDeclaredMethod(name, params);
			}
		} catch (NoSuchMethodException ex) {
			// ignore NoSuchMethodException
		} catch (NoSuchFieldException ex) {
			// ignore NoSuchMethodException
		} finally {
			if (result == null) {
				Class<?> superClz = clz.getSuperclass();
				if (superClz != null) {
					result = recusiveFindAccessibleObject(superClz, type, name,
							params);
				}
			}
		}
		return result;
	}

	/**
	 * 设方法可访问
	 * @param method
	 * @throws SecurityException
	 * @throws NoSuchMethodException
	 */
	public static void setMethodAccessibel(Method method) {
		Assert.notNull(method);
		if (!Modifier.isPublic(method.getModifiers())) {
			method.setAccessible(true);
		}
	}

	/**
	 * 设字段可访问
	 * @param field
	 * @throws SecurityException
	 * @throws NoSuchMethodException
	 */
	public static void setFieldAccessibel(Field field) {
		Assert.notNull(field);
		if (!Modifier.isPublic(field.getModifiers())) {
			field.setAccessible(true);
		}
	}

	public static Object getNullablePropertyValue(Object o, String propertyName) {
		BeanWrapper bw = new BeanWrapperImpl(o);
		Object value = null;
		try {
			value = bw.getPropertyValue(propertyName);
		} catch (NullValueInNestedPathException e) {
			// do nothing
		}
		return value;
	}
}
